package compiler.scanner.dfa;

import compiler.scanner.nfa.ETransition;
import compiler.scanner.nfa.NFAState;
import compiler.scanner.nfa.NFATransition;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.TreeSet;

/// <summary>
/// Summary description for DFAState.
/// </summary>
public class DFAState
{
    //~ Static fields/initializers ---------------------------------------------

    private static int NEXT_ID = 0;

    //~ Instance fields --------------------------------------------------------

    public final Map<Character, DFAState> transitions = new HashMap<Character, DFAState>();
    public final Set                      subStates;
    public String                         token;
    public boolean                        acceptingState;
    private final int                     id = nextId();

    //~ Constructors -----------------------------------------------------------

    public DFAState(Set subStates)
    {
        if (subStates.iterator().next() instanceof NFAState)
        {
            Set<NFAState> eClosureStates = subStates;

            this.subStates = eClosureStates;

            for (NFAState state : eClosureStates)
            {
                if (state.isAcceptable() && !acceptingState)
                {
                    acceptingState = true;
                    token          = state.getToken();
                }
                else if (state.isAcceptable() && acceptingState)
                {
                    if (!state.getToken().equals(token))
                        System.out.println(String.format("ERR: accepting two different tokens (%s ,%s) in one state (%d)", token, state.getToken(), getId()));
                }
            }
        }
        else // DFA substates; DFA minimization
        {
            Set<DFAState> equivSet = subStates;
            this.subStates = equivSet;

            for (DFAState state : equivSet)
            {
                if (state.acceptingState && !acceptingState)
                {
                    acceptingState = true;
                    token          = state.token;
                }
                else if (state.acceptingState && acceptingState)
                {
                    if (!state.token.equals(token))
                        System.out.println(String.format("ERR:(dfa-mini) accepting two different tokens (%s ,%s) in one state (%d)", token, state.token, getId()));
                }
            }
        }
    }

    //~ Methods ----------------------------------------------------------------

    public static void reset()
    {
        NEXT_ID = 0;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }

        if (getClass() != obj.getClass())
        {
            return false;
        }

        final DFAState other = (DFAState)obj;

        if (this.id != other.id)
        {
            return false;
        }

        return true;
    }

    public Set<NFAState> getAllTransitionsOn(Character c)
    {
        Set<NFAState> ret = new HashSet<NFAState>();

        for (NFAState nfaState : (Set<NFAState>)subStates)
        {
            for (NFATransition trans : nfaState.getTransitions())
            {
                if (!(trans instanceof ETransition) && (trans.getCharacter() == c))
                    for (NFAState target : trans.getDestinations())
                        ret.addAll(target.getEClosureStates());
            }
        }

        return ret;
    }

    public ArrayList<DFAState> getDfaStates()
    {
        ArrayList<DFAState> closedDFAStates = new ArrayList<DFAState>();
        Queue<DFAState>     openDFAStates = new LinkedList<DFAState>();
        DFAState            currDFAState = null;

        openDFAStates.add(this);

        do
        {
            currDFAState = openDFAStates.remove();

            for (DFAState dfaState : currDFAState.transitions.values())
            {
                if (!openDFAStates.contains(dfaState) && !closedDFAStates.contains(dfaState))
                {
                    openDFAStates.offer(dfaState);
                }
            }

            closedDFAStates.add(currDFAState);
        }
        while (openDFAStates.size() != 0);

        return (closedDFAStates);
    }

    public int getId()
    {
        return id;
    }

    public String getNfaIds()
    {
        String str = "{ ";

        if (subStates.iterator().next() instanceof NFAState)
        {
            Set<Integer> ids = new TreeSet<Integer>(); // make them sorted

            for (NFAState nfaState : (Set<NFAState>)subStates)
                ids.add(nfaState.getId());

            for (int i : ids)
                str += (i + ", ");
        }
        else
        {
            Set<Integer> ids = new TreeSet<Integer>(); // make it unique, cuz there might be duplication

            for (DFAState dfaState : (Set<DFAState>)subStates)
                for (NFAState nfaState : (Set<NFAState>)dfaState.subStates)
                    ids.add(nfaState.getId());

            for (int i : ids)
                str += (i + ", ");
        }

        return str + "}";
    }

    @Override
    public int hashCode()
    {
        int hash = 7;
        hash = (19 * hash) + this.id;

        return hash;
    }

    @Override
    public String toString()
    {
        return getNfaIds();
    }

    public void updateTransitionToEquivStates(Map<DFAState, DFAState> oldStateToNewStateMap)
    {
        Set<DFAState> equivSet = subStates;

        for (DFAState state : equivSet)
        {
            for (Map.Entry<Character, DFAState> trans : state.transitions.entrySet())
            {
                if (this.transitions.containsKey(trans.getKey()) && (oldStateToNewStateMap.get(this.transitions.get(trans.getKey())) != null) && !oldStateToNewStateMap.get(this.transitions.get(trans.getKey())).equals(oldStateToNewStateMap.get(trans.getValue())))
                    System.out.println("ERR(dfa-mini) two trans on the same character to different groups"); // supposedly can never happen

                this.transitions.put(trans.getKey(), oldStateToNewStateMap.get(trans.getValue()));
            }
        }
    }

    private static synchronized int nextId()
    {
        return ++NEXT_ID;
    }
}
